//
//  Filter.hpp
//  DemoV2
//
//  Created by 董一龙 on 2022/8/8.
//

#ifndef Filter_hpp
#define Filter_hpp

#include "../util/Trie.hpp"
#include <unordered_map>
#include <objc/runtime.h>
#include <string>
#include <initializer_list>
#include <tuple>

class Filter {
public:
    struct HitItem {
        uint32_t maxHitTimes = 0; ///< 最多可以被命中多少次，为0意为无限次，为1意为最多命中一次
        uint32_t curHitTimes = 0;
        /// EventID会作为testHitUID的命中时返回值，因为返回0代表未命中，所以不要设置hitEventID为0
        int32_t hitEventID; /// 自定义的命中时返回值，但不能是0
    };
    
    struct PrefixItem {
        std::string prefix;
        bool isWhiteList;
    };
    // 返回值小于0时黑名单事件，返回值大于0是白名单事件
    // 返回值为0是没有命中任何事件
    // 如果同时命中则白名单优先
    int32_t testMethodSignture(std::string const& method_signture) {
        if(m_methodSigntureMap.find(method_signture) == m_methodSigntureMap.end()) {
            return 0;
        }
        HitItem hitItem = m_methodSigntureMap[method_signture];
        if(hitItem.maxHitTimes == 0) {
            // do nothing
        } else if(hitItem.curHitTimes >= hitItem.maxHitTimes) {
            m_methodSigntureMap.erase(method_signture);
            return 0;
        } else {
            m_methodSigntureMap[method_signture].curHitTimes++;
        }
        return hitItem.hitEventID;
    }
    
    // 在白名单返回1，在黑名单返回-1，白名单优先
    int32_t testClassPrefix(std::string const& classname) {
        if(m_classPrefixTreeForWhiteList.hasPrefixMatch(classname)) {
            return 1;
        }
        else if(m_classPrefixTreeForBlackList.hasPrefixMatch(classname)) {
            return -1;
        }
        return 0;
    }
    
    int32_t testMethodPrefix(std::string const& methodname) {
        if(m_methodPrefixTreeForWhiteList.hasPrefixMatch(methodname)) {
            return 1;
        }
        else if(m_methodPrefixTreeForBlackList.hasPrefixMatch(methodname)) {
            return -1;
        }
        return 0;
    }
    void registerMethodSigntureHit(std::string const& method_signture, int32_t hitEventId, uint32_t maxHitTimes = 0) {
        m_methodSigntureMap[method_signture] = HitItem {
          .maxHitTimes = maxHitTimes,
          .curHitTimes = 0,
          .hitEventID = hitEventId,
        };
    }
    void registerClassPrefixHit(PrefixItem const& prefixItem) {
        if(prefixItem.isWhiteList) {
            m_classPrefixTreeForWhiteList.insertPrefix(prefixItem.prefix);
        }
        else {
            m_classPrefixTreeForBlackList.insertPrefix(prefixItem.prefix);
        }
    }
    void registerMethodPrefixHit(PrefixItem const& prefixItem) {
        if(prefixItem.isWhiteList) {
            m_methodPrefixTreeForWhiteList.insertPrefix(prefixItem.prefix);
        }
        else {
            m_methodPrefixTreeForBlackList.insertPrefix(prefixItem.prefix);
        }
    }
    void batchRegisterMethodSigntureHit(std::initializer_list<std::tuple<std::string, HitItem>> signtureWithhitItemList) {
        for(auto const& signtureAndhitItem : signtureWithhitItemList) {
            std::string signture;
            HitItem hitItem;
            std::tie(signture, hitItem) = signtureAndhitItem;
            registerMethodSigntureHit(signture, hitItem.hitEventID, hitItem.maxHitTimes);
        }
    }
    void batchRegisterMethodPrefixHit(std::initializer_list<PrefixItem> prefixList) {
        for(auto const& prefixItem : prefixList) {
            registerMethodPrefixHit(prefixItem);
        }
    }
    void batchRegisterClassPrefixHit(std::initializer_list<PrefixItem> prefixList) {
        for(auto const& prefixItem : prefixList) {
            registerClassPrefixHit(prefixItem);
        }
    }
    static std::string getMethodSignture(Class cls, SEL sel) {
        thread_local static char methodnameBuffer[1024];
        snprintf(methodnameBuffer, sizeof(methodnameBuffer), "%s[%s %s]",
                 class_isMetaClass(cls) ? "+" : "-",
                 class_getName(cls),
                 sel_getName(sel));
        return std::string(methodnameBuffer);
    }
    static size_t getMethodSigntureUid(std::string const& method_signture) {
        static std::hash<std::string> const hashFunc;
        return hashFunc(method_signture);
    }
private:
    Trie m_classPrefixTreeForWhiteList;
    Trie m_methodPrefixTreeForWhiteList;
    Trie m_classPrefixTreeForBlackList;
    Trie m_methodPrefixTreeForBlackList;
    std::unordered_map<std::string, HitItem> m_methodSigntureMap;
};

#endif /* Filter_hpp */
